feat(github): manage org/repo rulesets declaratively + add v* tag protection#70
Merged
Conversation
…tection Adopt the org's existing rulesets as Crossplane managed resources under deploy/rulesets/ (provider-upjet-github), the same Observe-first read-only flow used for repositories/ and teams/: - 10 org rulesets -> OrganizationRuleset (Observe), bound by ruleset id - ksail "Restrict deletions" + platform "Require merge queue" -> RepositoryRuleset (Observe), bound by <repo>:<id> - net-new "Protect release tags" OrganizationRuleset (managed, the only write): block tag deletion + force-move + require v<semver> on ~ALL repos/tags provider-upjet-github v0.19.1 cannot express the other 10 org rulesets (repository_property conditions, code_quality/copilot_code_review rules, repository-target rulesets) nor push rulesets nor the new Actions workflow-execution-protections; these stay UI-managed and are documented in deploy/rulesets/README.md and tracked in #69. Validated: kubectl kustomize deploy/ builds; every manifest passes kubectl apply --dry-run=server against the live CRDs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per the maintainer's one-resource-per-file preference: split the multi-doc organization-rulesets.yaml (10) and repository-rulesets.yaml (2) into one file per resource (filename = resource name, matching ../repositories/), and rename tag-protection.yaml -> protect-release-tags.yaml to match its resource name. Behaviour-preserving: the rendered set of 13 ruleset resources is byte-identical before/after the split (verified via kustomize build diff). kustomization.yaml + README.md updated for the new layout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 25, 2026
…-verb names deploy/rulesets/ mixed OrganizationRuleset and RepositoryRuleset. Split into one subfolder per CR kind, each with an active-verb filename convention: - organization-rulesets/ — OrganizationRuleset, named after the rule it enforces (e.g. require-pull-request.yaml); carries the convention README + importability matrix. - repository-rulesets/ — RepositoryRuleset as <verb>-on-<repo>.yaml (ksail-restrict-deletions -> restrict-deletions-on-ksail; platform-require-merge-queue -> require-merge-queue-on-platform). Pure rename + kustomization/README re-wiring; resource bodies (metadata.name, crossplane.io/external-name) unchanged. `kubectl kustomize deploy/` validated (11 OrganizationRuleset, 2 RepositoryRuleset). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
auto-merge was automatically disabled
June 25, 2026 18:23
Pull Request is not mergeable
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
The org's GitHub rulesets were the last major piece of org config still managed
only in the UI. This brings them into
deploy/as Crossplane managed resources(
provider-upjet-github), reconciled by the platformgithub-configtenant likerepositories/,teams/, andlabels/— and adds the tag protection the orglacked despite releasing on
v*tags.The platform side was already wired for this (the
organizationrulesets+repositoryrulesetsMRDs are activated and the tenant RBAC already covers both APIgroups); only the manifests were missing.
What's in this PR
organization-rulesets.yamlrepository-rulesets.yamlksailRestrict deletions,platformRequire merge queuetag-protection.yamlImports are Observe-first (
managementPolicies: ["Observe"]): read-only, zerobehaviour change, zero revert risk — identical to how repos/teams were adopted.
Deleteis omitted everywhere, so nothing here can ever delete a real ruleset. external-name binds
each to the existing ruleset (
<id>for org,<repo>:<id>for repo).The one write is
Protect release tags: an orgtagruleset over~ALLrepos/tagsthat blocks tag deletion + force-move and requires
v<semver>names. Creating newrelease tags is unaffected; only tampering is blocked.
Push / tag / Actions policies (as requested)
push-file rule types). Documented; tracked in Adopt remaining rulesets declaratively as provider-upjet-github gains support (10 org rulesets + push + Actions policies) #69.
(actor/event allow-lists via custom-property-scoped org rulesets) aren't adoptable yet
(new rule types absent; relies on
repository_propertyscoping the provider lacks).Documented; tracked in Adopt remaining rulesets declaratively as provider-upjet-github gains support (10 org rulesets + push + Actions policies) #69.
Provider gap — 10 of 22 stay UI-managed
provider-upjet-githubv0.19.1 can't expressrepository_propertyconditions,code_quality/copilot_code_reviewrules, orrepository-target rulesets, so 10 orgrulesets remain UI-managed (fully active meanwhile). Full matrix in
deploy/rulesets/README.md; re-adoption tracked in #69.Validation
kubectl kustomize deploy/builds (13 ruleset resources emitted).kubectl apply --dry-run=serveragainst the live prod CRDs.Rollout
Draft until you promote it. On merge + a
v*release tag of this repo, the artifactrepublishes and Crossplane reconciles: the 12 imports become
Readywithstatus.atProviderpopulated, andProtect release tagsis created. Promoting an importpast Observe to full management is a deliberate, separately-validated follow-up (backfill
forProviderfrom observed state first — the round-trip is lossy).